const crypto = require('crypto');

class VotingState {
  constructor(config = {}) {
    const { participants = [], votingDurationSeconds = 300 } = config;
    this._defaultDurationMs = Math.max(10, Number(votingDurationSeconds || 300)) * 1000;
    this._baseParticipants = this._normalizeParticipants(participants);
    if (this._baseParticipants.length === 0) {
      throw new Error('config.participants 至少需要包含一名候选人');
    }
    this.reset();
  }

  _normalizeParticipants(participants) {
    if (!Array.isArray(participants)) {
      return [];
    }
    return participants
      .map((participant) => {
        if (typeof participant === 'string') {
          return { id: null, name: participant.trim(), votes: 0 };
        }
        if (participant && typeof participant.name === 'string') {
          return {
            id: participant.id || null,
            name: participant.name.trim(),
            votes: Number(participant.votes) > 0 ? Number(participant.votes) : 0
          };
        }
        return null;
      })
      .filter((entry) => entry && entry.name.length > 0);
  }

  _assignIds(participants) {
    return participants.map((participant, index) => ({
      ...participant,
      id: participant.id || crypto.randomUUID(),
      votes: Number(participant.votes) > 0 ? Number(participant.votes) : 0,
      order: index
    }));
  }

  reset(options = {}) {
    const { participants, durationSeconds } = options;
    const normalizedList = this._normalizeParticipants(
      participants && participants.length ? participants : this._baseParticipants
    );
    if (normalizedList.length === 0) {
      throw new Error('重置失败：participants 不能为空');
    }
    this._participants = this._assignIds(normalizedList);
    this._durationMs = this._resolveDuration(durationSeconds);
    this._startedAt = Date.now();
    this._endsAt = this._startedAt + this._durationMs;
    this._closed = false;
  }

  _resolveDuration(durationSeconds) {
    if (Number.isFinite(Number(durationSeconds)) && Number(durationSeconds) > 0) {
      return Number(durationSeconds) * 1000;
    }
    return this._defaultDurationMs;
  }

  _computeOpenStatus(now = Date.now()) {
    if (this._closed) {
      return { isOpen: false, now };
    }
    if (now >= this._endsAt) {
      this._closed = true;
      return { isOpen: false, now };
    }
    return { isOpen: true, now };
  }

  _cloneParticipants() {
    return this._participants.map(({ order, ...rest }) => ({ ...rest }));
  }

  getState() {
    const { isOpen, now } = this._computeOpenStatus();
    const secondsRemaining = Math.max(0, Math.ceil((this._endsAt - now) / 1000));
    const maxVotes = this._participants.reduce((acc, participant) => {
      return participant.votes > acc ? participant.votes : acc;
    }, 0);
    const leaderIds = maxVotes > 0
      ? this._participants.filter((participant) => participant.votes === maxVotes).map((participant) => participant.id)
      : [];
    return {
      participants: this._cloneParticipants(),
      startedAt: this._startedAt,
      endsAt: this._endsAt,
      now,
      secondsRemaining,
      status: isOpen ? 'open' : 'closed',
      leaders: leaderIds,
      durationSeconds: Math.round(this._durationMs / 1000)
    };
  }

  isOpen() {
    return this._computeOpenStatus().isOpen;
  }

  vote(participantId) {
    if (!participantId) {
      return { accepted: false, reason: 'missing_participant' };
    }
    const { isOpen } = this._computeOpenStatus();
    if (!isOpen) {
      return { accepted: false, reason: 'voting_closed' };
    }
    const participant = this._participants.find((entry) => entry.id === participantId);
    if (!participant) {
      return { accepted: false, reason: 'invalid_participant' };
    }
    participant.votes += 1;
    return { accepted: true, participant: { id: participant.id, name: participant.name, votes: participant.votes } };
  }
}

module.exports = VotingState;
